13 - Networking & Communications

UART and I2C Communication

This week was about networking and communication. This includes various wired and wireless communication protocols. For this week, we had to explore some of them. More precisely, the assignments were:

  • Group Assignment
    • Send a message between two projects
  • Individual Assignment
    • Design, build, and connect wired or wireless node(s) with network or bus addresses and local input &/or output device(s)

Group Assignment

For the group assignment, my fab mates and me decided on exploring I2C and the UART communication. These are both wired communications which are most often employed in microcontrollers. Have a look at the group assignment page for the details.

For I2C communication, there is a way of checking whether the sending of the master node was successful. Here, after a message was send, the transmission is ended. Here, the method of ending it actually returns an integer. In this case, it can be stored and send via the serial communication to the computer. This was especially helpful to find if there is a mistake and a hint on what it could be. This is the code:

int status_wire = Wire.endTransmission();
Serial.println(status_wire);

In this case, this method for trouble shooting allowed us to discover, that the SCL and SDA pins of the Seeeduino we all used in our electronics production week must be pulled high. Other than that, implementing the I2C communication was relatively easy.

The UART communication was already touched in previous (individual) assignments. Therefore, it was quite straight forward to implement this as well. The only aspect that can be confusing is the connection of the Rx pin of one device to the Tx pin of the other and vice versa. This is due to the fact that the Rx pin receives something from another deice and the Tx pin transmits something to another device.

Individual Assignment

There are mainly two (wired) communication protocols that are almost always implemented in a microcontroller, namely UART and I2C. For the communication with UART, which stands for "Universal Asynchronous Receiver/Transmitter", two wires are needed which transmits data between two devices. Each wire is used unidirectional, i.e. it transmits from one device to the receiving device. Therefore the transmitting pin of the first device is connected to the receiving pin of the other device and vice versa.

The second communication protocol is I2C, which is an abbreviation for Inter-Integrated Circuit. It differs from UART in many ways. For example, multiple devices can be used, where one is the so-called master and the remaining the slaves. All devices are connected with only two wires, the serial data line (SDA) and the serial clock line (SCL). In order to reach certain slave devices, an address system is used.

I had explored UART communication already to e.g. send messages between my computer and my final project board as shown here. Furthermore, I will not need any wireless communication for my final project. Therefore, I decided on using I2C communication despite the fact that I never had done anything with this protocol except for the group assignment. However, I wanted to try and therefore explored this communication by using my final project board and the Seeeduino board.

I did not exactly know what my board should do but then, I remembered that I programmed a CircuitPython script during the embedded programming assignment which consists of a counter that keeps track of the number of times a button was pressed. Then, an LED is flashed as many times as the counter's value. Additionally, if the button is pressed for longer than one second, the counter is reset. During the group assignment, I also programmed the same script in the Arduino IDE which is shown here.

As I had this script available, I firstly explored some basics to implement an I2C communication and later modified the script with the counter to run on the two boards communicating the value of the counter.

Basics of Programming an I2C Communication in the Arduino IDE

With this script, I wanted the value of the counter being transmitted from one board to the other. Therefore, I simply added some lines for the I2C communication. Firstly, the library must be included.

// Include the required Wire library for I2C
#include <Wire.h>

Next, the communication must be initialized with for the master node

// Start I2C communication
Wire.begin();

and with

// Start the I2C Bus as Slave on specified address 
Wire.begin(i2c_address);

for the slave node. From the master node's perspective, the addresses are implemented later, when a message is send to a specific address. This is done with

Wire.beginTransmission(i2c_address);    // transmit to device specified with i2c address
Wire.write(x);                    	// sends value of x
Wire.endTransmission();    		// stop transmitting

Here, the I2C address of the transmission in the master's code and the initialization of the communication in the slave's code must match. In case the slave receives a message, a function can be triggered. For this, the following code should be inserted right after the initialization of the communication.

// Attach a function to trigger when something is received
Wire.onReceive(receiveEvent);

Then, a new function can be inserted which can e.g. look like the following.

void receiveEvent(int bytes) {
	x = Wire.read();    // read one character from the I2C
}

In this case, the function simply reads a single character from the communication.

On the other hand, the master slave can also receive a message however only upon request. This can be done with the following code.

//Request bytes from a peripheral device
Wire.requestFrom(i2c_address); 

In order to then receive a message upon request, the slave must have another function triggered

// Attach a function to trigger when something is requested
Wire.onRequest(handler);

This again needs the definition of the function which is triggered. This function can for example just send the message "hello" as shown below.

void handler(){
	Wire.write("hello "); 
}

I2C Communication for Messaging the Counter

With the basics at hand, it was actually rather easy to implement the messaging of the counter via the I2C communication. I started by adding the library and initializing the communication in the previously programmed code shown here which will be the code of the master node. Then, I removed all lines that have to do with the LED as an output and pasted it into the according place in a new Arduino sketch. I also changed the for-loop that flashes the LED with sending the value of the counter via the communication with the following lines.

// Send value of counter via I2C
Wire.beginTransmission(i2c_address); 	// transmit to device specified with i2c address
Wire.write(counter);                    // sends value of counter
Wire.endTransmission();    		// stop transmitting

With this, I was almost done with the master's code. I only shifted some lines to make it look cleaner. The full code is shown below.

// Include the required Wire library for I2C
#include <Wire.h>

const int buttonPin = 10;    // the number of the pushbutton pin
int counter = 0;            // counter to keep track of button pushed

unsigned long startTime;    // variable for time when it is started to press the button
unsigned long stopTime;     // variable for time when it is stopped to press the button

const int i2c_address = 9;  // set address for 12c of slave

void setup() {
	// initialize the pushbutton pin as an input:
	pinMode(buttonPin, INPUT);

	// Start I2C communication
	Wire.begin();
}

void loop() {
	// check if the pushbutton is pressed. If it is, the buttonState is LOW:
	if (digitalRead(buttonPin) == HIGH) {

	// save the time of start pressing
	startTime = millis();
	// while the button is continued to being pressed
	while (digitalRead(buttonPin) == HIGH);
	// Record time of stop pressing
	stopTime = millis();

	// Advance counter
	counter = counter +1;

	// if button was pressed for less than a second
	if ((stopTime-startTime) > 1000){
		// Reset counter
		counter = 0;
	}  
	// Send value of counter via I2C
	Wire.beginTransmission(i2c_address); // transmit to device specified with i2c address
	Wire.write(counter);                    // sends value of counter
	Wire.endTransmission();    // stop transmitting
	delay(100);  
	}
}

For the slave's code, I also added the library of the communication and initialized it. Then, I declared the pin of the LED pin and set this pin to an output mode. The critical part here was adding the function that is triggered when a message is received by the slave and the definition of this function. Basically, it reads the send value, stores it in the variable counter and then enters a for-loop that flashes the LED as many times as the value of the variable counter. This loop was already a part of the previous sketch without this communication. In total, the slave's code looked like the following code.

// Include the required Wire library for I2C
#include <Wire.h>

const int ledPin =  8;      // the number of the LED pin
const int i2c_address = 9;  // define address of this slave for I2C

// Initialize counter variables
int counter = 0;

void setup() {
	// initialize the LED pin as an output:
	pinMode(ledPin, OUTPUT);
	
	// Start the I2C Bus as Slave on specified address 
	Wire.begin(i2c_address); 
	// Attach a function to trigger when something is received.
	Wire.onReceive(receiveEvent);
}

void receiveEvent(int bytes) {
	counter = Wire.read();    // read one character from the I2C
	for (int i = 0; i < counter; i++){
		digitalWrite(ledPin, HIGH);   // turn the LED on (HIGH is the voltage level)
		delay(150);                   // wait for a second
		digitalWrite(ledPin, LOW);    // turn the LED off by making the voltage LOW
		delay(150);                   // wait for a second
		}
	}

void loop() {
}

After I had completed the code, I uploaded the master's code to the Seeeduino board and the slave's code to the final project board. The pin declarations for the button and LED were adjusted accordingly. For the details on uploading code to these boards, please refer to this and this documentation, respectively.

Then, I connected the 5 V, ground, SCL and SCA pins to each other and powered one of the boards using the USB hub of my computer and the according cable. This worked exactly as I intended. Have a look yourself!

Sending the Counter Via I2C Communication

Source Code for Download

  • Arduino Master Sketch (.ino): Arduino sketch for the master node sending an integer via I2C consisting of a counter for the number of times a button was pressed
  • Arduino Slave Sketch (.ino): Arduino sketch for the slave node blinking an LED as many times as the value of an integer received via I2C